package com.haima.sage.bigdata.api.frame.process.impl;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.haima.sage.bigdata.api.common.EsUtils;
import com.haima.sage.bigdata.api.entity.api.Api;
import com.haima.sage.bigdata.api.entity.api.data.config.EsApiConfig;
import com.haima.sage.bigdata.api.frame.process.ApiProcess;
import com.haima.sage.bigdata.api.entity.api.data.ApiData;
import com.haima.sage.bigdata.api.entity.api.data.cases.Case;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;

public class EsApiProcess extends BaseApiProcess<RestClient, Map<String, Object>> {
    private static final Logger LOG = LogManager.getLogger(EsApiProcess.class);
    private String method;
    private String uri;
    private Map<String, String> params;
    private Pattern getPattern = Pattern.compile("/[^/]+/[^/]+/[^/]+");
    private Pattern getSourcePattern = Pattern.compile("/[^/]+/[^/]+/[^/]+/_source");

    private EsApiProcess(Api api, ApiData data, Case item) throws Exception {
        super(api, data, item);
        EsApiConfig config = (EsApiConfig) item.getConfig();
        this.uri = config.getUri();
        if (StringUtils.isBlank(uri)) {
            uri = "/_search";
        }
        this.method = config.getMethod();
        if (StringUtils.isBlank(this.method)) {
            this.method = "GET";
        }
        this.params = config.getParams();
        if (this.params == null) {
            this.params = Collections.emptyMap();
        }
    }

    @Override
    protected RestClient getClient() {
        String key = "default-es";
        if (item.getConfig().getStorage() != null) {
            key = item.getConfig().getStorage();
        }
        return EsUtils.getClient(key);
    }

    @Override
    protected Map<String, Object> getResult(String dsl, RestClient client) throws IOException {
        Map<String, String> params = Collections.emptyMap();
        Response response = null;
        if (StringUtils.isBlank(dsl)) {
            response = client.performRequest(method, uri, params);
        } else {
            HttpEntity entity = new NStringEntity(dsl, ContentType.APPLICATION_JSON);
            response = client.performRequest(method, uri, params, entity);
        }
        Integer statusCode = response.getStatusLine().getStatusCode();
        if (statusCode.toString().startsWith("2")) {
            String responseBody = EntityUtils.toString(response.getEntity());
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> map = mapper.readValue(responseBody, Map.class);
            return map;
        }
        return null;
    }

    @Override
    protected Object getRowResult(String dsl, Map<String, Object> map) throws Exception {
        if (uri.endsWith("/_search")) {
            List<Map<String, Object>> list = getSearchHits(map);
            if (list != null && list.size() > 0) {
                return list.get(0);
            }
            return null;
        }
        return map;
    }

    @Override
    protected Object getPagerResult(String dsl, Map<String, Object> map) throws Exception {
        if (uri.endsWith("/_search")) {
            List<Map<String, Object>> list = getSearchHits(map);
            Long total = getSearchCount(map);
            Map<String, Object> dist = new HashMap<>();
            dist.put("hits", list);
            dist.put("total", total);
            return dist;
        }
        return map;
    }

    @Override
    protected Object getColumnResult(String dsl, Map<String, Object> map) throws Exception {
        Map<String, Object> resMap = null;
        if (uri.endsWith("/_search")) {
            List<Map<String, Object>> list = getSearchHits(map);
            if (list != null && list.size() > 0) {
                resMap = (Map<String, Object>) list.get(0).get("_source");
            }
        } else if (uri.endsWith("/_count")) {
            return map.get("count");
        } else if (getPattern.matcher(uri).matches()) {
            resMap = getKeep(map, "_source");
        } else if (getSourcePattern.matcher(uri).matches()) {
            resMap = getKeep(map);
        }
        if (resMap != null) {
            for (Map.Entry<String, Object> entry : resMap.entrySet()) {
                return entry.getValue();
            }
        }
        return null;
    }

    private Map<String, Object> getKeep(Map<String, Object> map, String... args) {
        if (args == null) {
            return map;
        }
        Map<String, Object> tmp = map;
        for (String arg : args) {
            Object object = tmp.get(arg);
            if (object == null) {
                return null;
            }
            tmp = (Map<String, Object>) object;
        }
        return tmp;
    }

    private Long getSearchCount(Map<String, Object> map) {
        if (map != null) {
            Object hits = map.get("hits");
            if (hits != null && hits instanceof Map) {
                Object sub = ((Map) hits).get("total");
                if (sub != null && sub instanceof Number) {
                    return ((Number) sub).longValue();
                }
            }
        }
        return null;
    }

    private List<Map<String, Object>> getSearchHits(Map<String, Object> map) {
        if (map != null) {
            Object hits = map.get("hits");
            if (hits != null && hits instanceof Map) {
                Object sub = ((Map) hits).get("hits");
                if (sub != null && sub instanceof List) {
                    return (List<Map<String, Object>>) sub;
                }
            }
        }
        return null;
    }

    @Override
    protected Object getListResult(String dsl, Map<String, Object> map) throws Exception {
        if (uri.endsWith("/_search")) {
            return getSearchHits(map);
        }
        return map;
    }

    public static ApiProcess build(Api api, ApiData data, Case item) throws Exception {
        return new EsApiProcess(api, data, item);
    }

    @Override
    public Object execute(Map<String, ?> params) throws Exception {
        RestClient client = getClient();
        if (client == null) {
            throw new Exception("can't get client");
        }
        Map<String, String> map = porcessParamsAndConsts(params);
        String sql = interpreter.value(map);
        LOG.debug("config: {}", sql);
        Map<String, Object> rs = getResult(sql, client);
        if (api.getResult() == null) {
            return getListResult(sql, rs);
        } else {
            switch (api.getResult()) {
                case Column:
                    return getColumnResult(sql, rs);
                case Page:
                    return getPagerResult(sql, rs);
                case Row:
                    return getRowResult(sql, rs);
                case List:
                default:
                    return getListResult(sql, rs);
            }
        }
    }
}
